﻿/*!
 * This Source Code is subject to the terms of the Mozilla Public License
 * version 2.0 (the "License"). You can obtain a copy of the License at
 * http://mozilla.org/MPL/2.0/.
 *
 * The source code is Adblock Plus for Chrome. Modified by xiaody <xiaody09@163.com>
 */

var MyFilters = _MyFilters || null;

const MyList = [
	'https://adblock-chinalist.googlecode.com/svn/trunk/adblock-lazy.txt',
	'https://xiaody.googlecode.com/svn/trunk/adblock-filters.txt'
];

const DEBUG = 0;

const redirectTable = {
	'IMAGE' : '',
	'OTHER' : 'data:,'
}

const log = DEBUG ? function(msg){console.log(msg)} : function(){};

var Matcher = { // window.Matcher
	cacheMap : new Map(),
	filterByKeyword : {__proto__: null},
	add : function(filter){
		switch(filter.contentType){
			case RegExpFilter.typeMap.DONOTTRACK:
			case RegExpFilter.typeMap.POPUP:
				break;
			case RegExpFilter.typeMap.ELEMHIDE:
				ElemHide.whiteListFilters.push(filter);
				break;
			default:
				var keyword = this.findKeyword(filter);
				switch(typeof this.filterByKeyword[keyword]){
					case 'undefined':
						this.filterByKeyword[keyword] = [filter];
						break;
					default: // Make sure the white-lists are always at first;
						if(/^@@/.test(filter.text))
							this.filterByKeyword[keyword].unshift(filter);
						else
							this.filterByKeyword[keyword].push(filter);
				}
		}
	},
	findKeyword : function(filter){
		'use strict';
		var defaultResult = filter.contentType & RegExpFilter.typeMap.DONOTTRACK ? 'donottrack' : '';
		var text = filter.text;

		if (Filter.regexpRegExp.test(text)){
			// The filter is a regular expression - private patch by xiaody
			if(/\$.*keyword=([a-z0-9%]{3,})/i.test(text))
				return RegExp.$1;
			return defaultResult;
		}
		if(Filter.optionsRegExp.test(text))
			text = RegExp.leftContext;
		if(text.substr(0, 2) === '@@')
			text = text.substr(2);
		var candidates = text.toLowerCase().match(/[^a-z0-9%*][a-z0-9%]{3,}(?=[^a-z0-9%*])/g);
		if(!candidates)
			return defaultResult;
		var hash = this.filterByKeyword;
		var result = defaultResult, resultCount = 0xFFFFFF, resultLength = 0;
		for(let candidate, count, i = 0, l = candidates.length;  i < l; ++i){
			candidate = candidates[i].substr(1);
			switch(typeof hash[candidate]){
				case 'undefined':
					count = 0;  break;
				default:
					count = hash[candidate].length;
			}
			if(count < resultCount || count === resultCount && candidate.length > resultLength){
				result = candidate;
				resultCount = count;
				resultLength = candidate.length;
			}
		} return result;
	},
	checkEntryMatch : function(word, b, c, d, j){
		"use strict";
		var array = this.filterByKeyword[word], filter;
		for(let i=0, l=array.length; i<l; ++i){
			filter = array[i];
			if(filter.matches(b,c,d,j)) return filter;
		} return null;
	},
	matchesAny : function(location, contentType, docDomain, thirdParty){
		"use strict";
		var map = this.cacheMap, cKey = location.replace(/\d{10,}/g, '');
		if (map.has(docDomain) === false)
			return this._matchesAnyInternal(location, contentType, docDomain, thirdParty);
		if (map.get(docDomain).has(cKey) === true)
			return map.get(docDomain).get(cKey);
		if (contentType === 'IMAGE' || contentType === 'OBJECT' || location.length > 128)
			return this._matchesAnyInternal(location, contentType, docDomain, thirdParty);
		return map.get(docDomain).set(cKey, this._matchesAnyInternal(location, contentType, docDomain, thirdParty));
	},
	_matchesAnyInternal : function(location, contentType, docDomain, thirdParty){
		"use strict";
		var keywords = location.toLowerCase().match(/[a-z0-9%]{3,}/g) || [],
			afterall = false;
		if(contentType === 'DONOTTRACK') keywords.push('donottrack');
		else keywords.push('');
		for(let substr, result, i = 0, l = keywords.length; i < l; ++i){
			substr = keywords[i];
			if(typeof this.filterByKeyword[substr] !== 'undefined'){
				result = this.checkEntryMatch(substr, location, contentType, docDomain, thirdParty);
				if(!result) continue;
				if(result instanceof WhitelistFilter) return false;
				else afterall = true;
			}
		} return afterall;
	},
	__proto__: null
};

var ElemHide = { // window.ElemHide
	addingCache : [], _defaultSelectors : [],
	math : 0, filterByMath : [],
	whiteListFilters : [],
	isWhitelisted : function(href){
		"use strict";
		for(let i = this.whiteListFilters.length; i--;){
			if(this.whiteListFilters[i].regexp.test(href))
				return true;
		} return false;
	},
	add : function(filter){
		if(!filter.domains)
			this.addingCache.push(filter.selector);
		else
			this.filterByMath[this.math++] = filter;
	},
	cacheDefault : function(){
		if(this.addingCache.length === 0) return;
		const group_length = 0xFF;
		var group = '';
		while(this.addingCache.length > group_length){
			group = this.addingCache.splice(0, group_length).join(',');
			this._defaultSelectors.push(group);
		} this._defaultSelectors.push(this.addingCache.join(','));
		this.addingCache = [];
	},
	getSelectors : function(domain, href, cachedFlag) {
		"use strict";
		if(this.isWhitelisted(href))
			return ['#__adblock_null_selector__'];
		if(cachedFlag)
			return this._defaultSelectors;
		var result = [];
		for(let i = this.math, f; i--;){
			f = this.filterByMath[i];
			if(f.isActiveOnDomain(domain))
				result.push(f.selector);
		}
		result = result.join(',');
		return this._defaultSelectors.concat(result);
	},
	__proto__ : null
};

// Functions for download and add filters.
function execute(list, selfDefinedFilters){
	"use strict";

	if ( selfDefinedFilters ) {
		if(typeof selfDefinedFilters === 'string')  // Convert to array.
			selfDefinedFilters = selfDefinedFilters.split(/[\r\n]+/);
		for(let i = selfDefinedFilters.length; i--;)
			addFilter(selfDefinedFilters[i]);
	}

	if ( list ) {
		if(typeof list === 'string')
			addList(list);
		else if(Array.isArray(list))
			for(let i = 0, l = list.length; i < l; ++i)
				addList(list[i]);
	}
}
function addList(url){
	if(!url)
		return;
	if(typeof localStorage[url] !== 'undefined'){
		addFilterByDoc(localStorage[url]);
	}
	var c = new XMLHttpRequest();
	c.open('GET', url);
	c.onreadystatechange = function(){
		var txt = '', lines = [], i;
		if(c.readyState === 4)switch(c.status){
			case 200:
			case 304:
				txt = c.responseText;
				if(/^\[Adblock.?Plus.?[\d\.]+?\]/i.test(txt)){
					if(typeof localStorage[url] === 'undefined'){
						addFilterByDoc(txt);
					} localStorage[url] = txt;
				}
				break;
			default:
				log('Filter list download failed.');
		} else return;

	};
	c.send(null);
}
function addFilterByDoc(txt){
	var lines = txt.split(/[\r\n]+/);
	for(var i = lines.length; --i;)  // Skip first line;
		addFilter(lines[i]);
	ElemHide.cacheDefault();
}	
function addFilter(text){
	if(!text || text[0] === '!') return;
	filter = Filter.fromText(Filter.normalize(text));
	if(/\w/.test(filter.text)){
		if(filter instanceof RegExpFilter)  Matcher.add(filter);
		else if(filter instanceof ElemHideFilter) ElemHide.add(filter);
	}
}


function onBeforeRequest(details){
	"use strict";
	var type = details.type, url = details.url, tabId = details.tabId,
		requestHost = extractDomain(url);
	if(type === 'main_frame'){
		if(typeof Matcher.cacheMap.has(requestHost) === false)
			Matcher.cacheMap.set(requestHost, new Map());
		recordFrame(tabId, 0, url, true); //(details.frameId===0) for main frames;
		return;
	}

	var topUrl = getTabUrl(tabId), documentUrl = getFrameUrl(tabId, details.frameId) || topUrl,
		documentHost = extractDomain(documentUrl);
	if(type === 'sub_frame'){
		type = 'SUBDOCUMENT';
		recordFrame(tabId, details.frameId, url, false);
		documentUrl = getFrameUrl(tabId, details.parentFrameId) || topUrl;
		documentHost = extractDomain(documentUrl);
	}else type = type.toUpperCase();

	var thirdParty = isThirdParty(requestHost, documentHost),
		toBlock = Matcher.matchesAny(url, type, documentHost, thirdParty);
	if(toBlock){
		switch(type){
			case 'IMAGE':
			case 'OBJECT':
			case 'SUBDOCUMENT':
				var relSrc = url.substr(url.indexOf('//') + 2);
				relSrc = relSrc.substr(relSrc.indexOf('/'));
				chrome.tabs.insertCSS( tabId, {
					code: '[src="' + url + '"], [src="' + relSrc + '"]{display:none!important}',
					allFrames: true,
					runAt : "document_start"
				} );
			default:
				return {redirectUrl : redirectTable[type] || redirectTable['OTHER']};
		}
	}

}


function extractDomain(url){
	"use strict";
	if (!url) return '';
	url = url.substr(3 + url.indexOf('://'));
	url = url.substr(0, url.indexOf('/'));
	return url.replace(/\.sixxs\.org$/, '');
}
function isThirdParty(requestHost, documentHost){
	"use strict";
	if(!/^\d+(\.\d+)*$/.test(documentHost) && /([^\.]+\.(?:com|edu|org|net)\.[^\.]{2,3}|[^\.]+\.[^\.]+)$/.test(documentHost))
		documentHost = RegExp.$1;
	if(requestHost.length > documentHost.length)
		return (requestHost.substr(requestHost.length - documentHost.length - 1) !== '.' + documentHost);
	else
		return (requestHost !== documentHost);
}
function recordFrame(tabId, frameId, frameUrl, isMain){
	"use strict";
	if(!(tabId in frames)) frames[tabId] = {};

	frames[tabId][frameId] = frameUrl;
	if(isMain) tabs[tabId] = frameUrl;
}
function getFrameUrl(tabId, frameId){
	"use strict";
	if(tabId in frames && frameId in frames[tabId])
		return frames[tabId][frameId];
	return null;
}
function getTabUrl(tabId){
	"use strict";
	return tabs[tabId] || null;
}
function forgetTab(tabId){
	"use strict";
	delete frames[tabId];
	delete tabs[tabId];
}

// Main block.
var frames = {__proto__: null}, tabs = {__proto__: null};
execute(MyList, MyFilters);

chrome.tabs.onRemoved.addListener(function(tabId){forgetTab(tabId)});
chrome.extension.onRequest.addListener(function(msg, _, Response){
	if(!msg.hostname)
		return;
	Response(ElemHide.getSelectors(msg.hostname, msg.href, msg.cached));
});
chrome.webRequest.onBeforeRequest.addListener(onBeforeRequest, {urls: ['http://*/*','https://*/*']}, ['blocking']);
